﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WarioOldies.Models;
using System.Net;
using System.Xml.Linq;
using HtmlAgilityPack;
using System.IO;
using System.Windows;

namespace GameListCreator.ViewModels
{
    /// <summary>
    /// Data type de l'aspirateur
    /// </summary>

    public enum VacuumDataTypes 
    {
        None = -1,
        GbDbUri,
        RomUri
    }

    /// <summary>
    /// Vacuum Result
    /// </summary>

    public struct VacuumResult
    {
        /// <summary>
        /// Type de data
        /// </summary>

        public VacuumDataTypes VacuumData
        {
            get;
            set;
        }

        /// <summary>
        /// Data
        /// </summary>

        public string Data
        {
            get;
            set;
        }
    }

    /// <summary>
    /// GameViewModel
    /// </summary>

    public class GameViewModel : ViewModel
    {
        /// <summary>
        /// Constructeur
        /// </summary>
        /// <param name="gameListViewModel"></param>

        public GameViewModel(GameListViewModel gameListViewModel)
        {
            this.GameListViewModel = gameListViewModel;
            this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(GameViewModel_PropertyChanged);
        }

        /// <summary>
        /// Changement d'une propriété
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        void GameViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName != GameViewModel.CheckMessagePropertyName)
            {
                this.Check();
            }
        }

        /// <summary>
        /// GameListViewModel
        /// </summary>

        public GameListViewModel GameListViewModel
        {
            get;
            private set;
        }

        /// <summary>
        /// Nom de la propriété Machine
        /// </summary>

        public const string MachinePropertyName = "Machine";

        /// <summary>
        /// propriété Machine :  
        /// </summary>

        public string Machine
        {
            get
            {
                return this._Machine;
            }

            set
            {
                if (this._Machine != value)
                {
                    this._Machine = value;
                    this.RaisePropertyChanged(MachinePropertyName);
                }
            }
        }

        private string _Machine = null;

        
        /// <summary>
        /// Nom de la propriété Label
        /// </summary>

        public const string LabelPropertyName = "Label";

        /// <summary>
        /// propriété Label :  
        /// </summary>

        public string Label
        {
            get
            {
                return this._Label;
            }

            set
            {
                if (this._Label != value)
                {
                    this._Label = value;
                    this.RaisePropertyChanged(LabelPropertyName);
                }
            }
        }

        private string _Label = null;

        
        /// <summary>
        /// Nom de la propriété Id
        /// </summary>

        public const string IdPropertyName = "Id";

        /// <summary>
        /// propriété Id :  
        /// </summary>

        public string Id
        {
            get
            {
                return this._Id;
            }

            private set
            {
                if (this._Id != value)
                {
                    this._Id = value;
                    this.RaisePropertyChanged(IdPropertyName);
                }
            }
        }

        private string _Id = null;

        
        /// <summary>
        /// Nom de la propriété Genre
        /// </summary>

        public const string GenrePropertyName = "Genre";

        /// <summary>
        /// propriété Genre :  
        /// </summary>

        public string Genre
        {
            get
            {
                return this._Genre;
            }

            set
            {
                if (this._Genre != value)
                {
                    this._Genre = value;
                    this.RaisePropertyChanged(GenrePropertyName);
                }
            }
        }

        private string _Genre = null;

        
        /// <summary>
        /// Nom de la propriété Language
        /// </summary>

        public const string LanguagePropertyName = "Language";

        /// <summary>
        /// propriété Language :  
        /// </summary>

        public string Language
        {
            get
            {
                return this._Language;
            }

            set
            {
                if (this._Language != value)
                {
                    this._Language = value;
                    this.RaisePropertyChanged(LanguagePropertyName);
                }
            }
        }

        private string _Language = null;

        
        /// <summary>
        /// Nom de la propriété Serie
        /// </summary>

        public const string SeriePropertyName = "Serie";

        /// <summary>
        /// propriété Serie :  
        /// </summary>

        public string Serie
        {
            get
            {
                return this._Serie;
            }

            set
            {
                if (this._Serie != value)
                {
                    this._Serie = value;
                    this.RaisePropertyChanged(SeriePropertyName);
                }
            }
        }

        private string _Serie = null;

        
        /// <summary>
        /// Nom de la propriété FrontCoverRelativeUri
        /// </summary>

        public const string FrontCoverRelativeUriPropertyName = "FrontCoverRelativeUri";

        /// <summary>
        /// propriété FrontCoverRelativeUri :  
        /// </summary>

        public string FrontCoverRelativeUri
        {
            get
            {
                return this._FrontCoverRelativeUri;
            }

            set
            {
                if (this._FrontCoverRelativeUri != value)
                {
                    this._FrontCoverRelativeUri = value;
                    this.FrontCoverFullFilename = this.GetCoverFullFilename(value);
                    this.RaisePropertyChanged(FrontCoverRelativeUriPropertyName);
                }
            }
        }

        private string _FrontCoverRelativeUri = null;

        
        /// <summary>
        /// Nom de la propriété FrontCoverFullFilename
        /// </summary>

        public const string FrontCoverFullFilenamePropertyName = "FrontCoverFullFilename";

        /// <summary>
        /// propriété FrontCoverFullFilename :  
        /// </summary>

        public string FrontCoverFullFilename
        {
            get
            {
                return this._FrontCoverFullFilename;
            }

            private set
            {
                if (this._FrontCoverFullFilename != value)
                {
                    this._FrontCoverFullFilename = value;
                    this.RaisePropertyChanged(FrontCoverFullFilenamePropertyName);
                }
            }
        }

        private string _FrontCoverFullFilename = null;        

        /// <summary>
        /// Nom de la propriété RomRelativeUri
        /// </summary>

        public const string RomRelativeUriPropertyName = "RomRelativeUri";

        /// <summary>
        /// propriété RomRelativeUri :  
        /// </summary>

        public string RomRelativeUri
        {
            get
            {
                return this._RomRelativeUri;
            }

            set
            {
                if (this._RomRelativeUri != value)
                {
                    this._RomRelativeUri = value;
                    this.RomFullFilename = this.GetRomFullFilename(value);
                    this.RaisePropertyChanged(RomRelativeUriPropertyName);
                }
            }
        }

        private string _RomRelativeUri = null;

        
        /// <summary>
        /// Nom de la propriété RomFullFilename
        /// </summary>

        public const string RomFullFilenamePropertyName = "RomFullFilename";

        /// <summary>
        /// propriété RomFullFilename :  
        /// </summary>

        public string RomFullFilename
        {
            get
            {
                return this._RomFullFilename;
            }

            private set
            {
                if (this._RomFullFilename != value)
                {
                    this._RomFullFilename = value;
                    this.RaisePropertyChanged(RomFullFilenamePropertyName);
                }
            }
        }

        private string _RomFullFilename = null;

        /// <summary>
        /// Description du jeux
        /// </summary>

        
        /// <summary>
        /// Nom de la propriété Description
        /// </summary>

        public const string DescriptionPropertyName = "Description";

        /// <summary>
        /// propriété Description :  
        /// </summary>

        public string Description
        {
            get
            {
                return this._Description;
            }

            set
            {
                if (this._Description != value)
                {
                    this._Description = value;
                    this.RaisePropertyChanged(DescriptionPropertyName);
                }
            }
        }

        private string _Description = null;

        
        /// <summary>
        /// Nom de la propriété Publisher
        /// </summary>

        public const string PublisherPropertyName = "Publisher";

        /// <summary>
        /// propriété Publisher :  
        /// </summary>

        public string Publisher
        {
            get
            {
                return this._Publisher;
            }

            set
            {
                if (this._Publisher != value)
                {
                    this._Publisher = value;
                    this.RaisePropertyChanged(PublisherPropertyName);
                }
            }
        }

        private string _Publisher = null;

        /// <summary>
        /// Parse
        /// </summary>
        /// <param name="game"></param>

        public static GameViewModel Create(GameListViewModel list, Game game)
        {
            GameViewModel g = new GameViewModel(list);

            g.Description = game.Description;
            g.FrontCoverRelativeUri = game.FrontCoverUri;
            g.Genre = game.Genre;
            g.Id = game.Id;
            g.Label = game.Label;
            g.Language = game.Language;
            g.Machine = game.Machine;
            g.Publisher = game.Publisher;
            g.RomRelativeUri = game.RomUri;
            g.Serie = game.Serie;

            return g;
        }

        /// <summary>
        /// Parse
        /// </summary>
        /// <param name="game"></param>

        public Game Convert()
        {
            Game g = new Game();

            g.Description = this.Description;
            g.FrontCoverUri = this.FrontCoverRelativeUri;
            g.Genre = this.Genre;
            g.Id = this.Id;
            g.Label = this.Label;
            g.Language = this.Language;
            g.Machine = this.Machine;
            g.Publisher = this.Publisher;
            g.RomUri = this.RomRelativeUri;
            g.Serie = this.Serie;

            return g;
        }
        
        /// <summary>
        /// Nom de la propriété IsChecked
        /// </summary>

        public const string IsCheckedPropertyName = "IsChecked";

        /// <summary>
        /// propriété IsChecked :  
        /// </summary>

        public bool IsChecked
        {
            get
            {
                return this._IsChecked;
            }

            private set
            {
                if (this._IsChecked != value)
                {
                    this._IsChecked = value;
                    this.RaisePropertyChanged(IsCheckedPropertyName);
                }
            }
        }

        private bool _IsChecked = false;

        /// <summary>
        /// Vérifier
        /// </summary>

        private void Check()
        {
            this.IsChecked = (
                string.IsNullOrWhiteSpace(this.Label) == false &&
                string.IsNullOrWhiteSpace(this.RomRelativeUri) == false
                );

            if (this.IsChecked == false)
            {
                if (string.IsNullOrWhiteSpace(this.RomRelativeUri) == true)
                {
                    this.CheckMessage = "Rom file is missing ! Please Drop a rom or press the '...' button of the Rom Id TextBox.";
                }
                
                if (string.IsNullOrWhiteSpace(this.Label) == true)
                {
                    this.CheckMessage = "Name of the game is missing !";
                    return;
                }
            }
        }

        
        /// <summary>
        /// Nom de la propriété CheckMessage
        /// </summary>

        public const string CheckMessagePropertyName = "CheckMessage";

        /// <summary>
        /// propriété CheckMessage :  Message mise en place par la methode Check
        /// </summary>

        public string CheckMessage
        {
            get
            {
                return this._CheckMessage;
            }

            set
            {
                if (this._CheckMessage != value)
                {
                    this._CheckMessage = value;
                    this.RaisePropertyChanged(CheckMessagePropertyName);
                }
            }
        }

        private string _CheckMessage = null;

        /// <summary>
        /// Aspirateur à partir de copier-coller / drop
        /// </summary>
        /// <param name="data"></param>

        public VacuumResult Vacuum(IDataObject data)
        {
            if (data.GetDataPresent("HTML Format") == true)
            {
                string htmlFormat = (string)data.GetData("HTML Format");

                string[] htmlInfos = htmlFormat.Split(new string[] { "\r\n" }, 9, StringSplitOptions.None);

                string[] sourceInfos = htmlInfos[7].Split(new char[] { ':' }, 2);

                if (sourceInfos[0] != "SourceURL")
                {
                    throw new Exception("No SourceHTML detected in HTML Format !");
                }

                if (sourceInfos[1] == null)
                {
                    throw new Exception("No valid value sourceHTML detected in HTML Format !");
                }

                if (Path.GetExtension(sourceInfos[1]) == ".jpg")
                {
                    this.DownloadCoverFile(sourceInfos[1]);
                }
                else
                {
                    if (sourceInfos[1].StartsWith("http://www.gbdb.org/games.php?title=") == false)
                    {
                        throw new Exception("The HTML page is not a game page from http://www.gbdb.org !");
                    }
                    
                    try
                    {
                        this.VacuumGbDbGameHtml(htmlInfos[8]);
                        return new VacuumResult() { VacuumData = VacuumDataTypes.GbDbUri, Data = sourceInfos[1] };
                    }
                    catch
                    {
                        throw new Exception("Error detected during the analyse of the HTML file !");
                    }
                }
            }

            // Url

            if (data.GetDataPresent("Text") == true)
            {
                string url = (string)data.GetData("Text");

                Uri uri;

                if (Uri.TryCreate(url, UriKind.Absolute, out uri) == true)
                {
                    if (url.StartsWith("http://www.gbdb.org/games.php?title=") == false)
                    {
                        throw new Exception("The HTML page is not a game page from http://www.gbdb.org !");
                    }

                    this.VacuumGbDbGameUri(url);

                    return new VacuumResult() { VacuumData = VacuumDataTypes.GbDbUri, Data = url };
                }
            }

            // Fichier dropppé
            
            if (data.GetDataPresent("FileDrop") == true)
            {
                string[] filenames = (string[])data.GetData("FileDrop");

                switch (Path.GetExtension(filenames[0]).ToLower())
                {
                    case ".jpg" :

                        this.DownloadCoverFile(filenames[0]);
                        
                        break;

                    case ".sgb":
                    case ".gb":

                        this.VacuumRom(filenames[0]);

                        break;

                    default :
                        throw new Exception("This type of file is not allowed !");
                }
            }

            return new VacuumResult() { VacuumData = VacuumDataTypes.None };
        }

        /// <summary>
        /// Aspirateur d'information de la base 
        /// </summary>
        /// <param name="p"></param>

        public void VacuumGbDbGameUri(string gbdbUriString)
        {
            Uri uri = null;
            
            if( Uri.TryCreate(gbdbUriString, UriKind.Absolute, out uri ) == true )
            {
                WebClient client = new WebClient();

                string html = client.DownloadString(uri);

                this.VacuumGbDbGameHtml(html);
            }
        }

        /// <summary>
        /// Aspirateur HTML de page GbDb
        /// </summary>
        /// <param name="html"></param>

        public void VacuumGbDbGameHtml(string html)
        {
            HtmlDocument document = new HtmlDocument();

            document.LoadHtml(html);

            HtmlNode contentNode = document.GetElementbyId("content");

            if (contentNode != null)
            {
                HtmlNode labelNode = contentNode.Element("h1");

                this.Label = labelNode.InnerText;

                // leftData

                HtmlNode leftDataNode = document.GetElementbyId("leftdata");

                HtmlNode pNode = leftDataNode.Element("p");

                foreach (HtmlNode strongNode in pNode.Elements("strong"))
                {
                    if (strongNode.InnerText.StartsWith("Publisher"))
                    {
                        this.Publisher = strongNode.NextSibling.InnerText.Trim();
                        break;
                    }
                }

                HtmlNode rightDataNode = document.GetElementbyId("rightdata");

                pNode = rightDataNode.Element("p");

                foreach (HtmlNode strongNode in pNode.Elements("strong"))
                {
                    if (strongNode.InnerText.StartsWith("Genre"))
                    {
                        this.Genre = strongNode.NextSibling.InnerText.Trim();
                        break;
                    }
                }

                pNode = rightDataNode.NextSibling;

                while (pNode != null && pNode.Name != "p")
                {
                    pNode = pNode.NextSibling;
                }

                if (pNode != null)
                {
                    string description = pNode.InnerText;

                    // si balise <p> mal fermé !
                    if (description == "")
                    {
                        HtmlNode nextNode = pNode.NextSibling;

                        while (nextNode != null)
                        {
                            if ((description == "" && nextNode.InnerText == "\r\n") == false)
                            {
                                description += nextNode.InnerText;
                            }

                            nextNode = nextNode.NextSibling;
                        }
                    }
    
                    const string boxBackString = "Box back";

                    if (description.StartsWith(boxBackString) == true)
                    {
                        description = description.Substring(boxBackString.Length);
                    }

                    description = description.Replace("â„¢", "");
                    description = description.Replace("Â®", "");

                    this.Description = description;
                }

                // Photo
                HtmlNode picsNode = document.GetElementbyId("pics");

                if (picsNode != null)
                {
                    if (picsNode.HasChildNodes == true)
                    {
                        string href = picsNode.FirstChild.GetAttributeValue("href", string.Empty);

                        if (href.EndsWith("_box_front.jpg") == true)
                        {
                            string sourceFilename = "http://www.gbdb.org/" + href;

                            this.DownloadCoverFile(sourceFilename);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Téléchargement de la couverture (uri ou local)
        /// </summary>
        /// <param name="sourceFilename"></param>

        private void DownloadCoverFile(string sourceFilename)
        {
            WebClient clientImage = new WebClient();

            string relativefilename = this.GetCoverRelativeFilename(Guid.NewGuid() + Path.GetExtension(sourceFilename) );
            string fullFilename = this.GetCoverFullFilename(relativefilename);

            this.GameListViewModel.EnsureDirectoryGameExists();

            this.DeleteCoverFile();

            clientImage.DownloadFile(sourceFilename, fullFilename);

            this.FrontCoverRelativeUri = relativefilename;
        }

        /// <summary>
        /// Destruction de la couverture
        /// </summary>

        public void DeleteCoverFile()
        {
            if (this.FrontCoverRelativeUri != null)
            {
                try
                {
                    if (File.Exists(this.FrontCoverFullFilename) == true)
                    {
                        File.Delete(this.FrontCoverFullFilename);
                    }
                }
                catch
                {
                }
                
                this.FrontCoverRelativeUri = null;
            }
        }

        /// <summary>
        /// Téléchargement de la Rom (uri ou local)
        /// </summary>
        /// <param name="sourceFilename"></param>

        private void DownloadRomFile(string sourceFilename)
        {
            WebClient clientImage = new WebClient();

            string relativefilename = this.GetRomRelativeFilename(Guid.NewGuid() + Path.GetExtension( sourceFilename ) );
            string fullFilename = this.GetRomFullFilename(relativefilename);

            this.GameListViewModel.EnsureDirectoryGameExists();

            this.DeleteRom();

            clientImage.DownloadFile(sourceFilename, fullFilename);

            this.RomRelativeUri = relativefilename;
        }

        /// <summary>
        /// Destruction de la Rom
        /// </summary>

        public void DeleteRom()
        {
            if (this.RomRelativeUri != null)
            {
                try
                {
                    if (File.Exists(this.RomFullFilename) == true)
                    {
                        File.Delete(this.RomFullFilename);                    
                    }
                }
                catch
                {
                }

                this.RomRelativeUri = null;
            }
            
            this.Id = null;
        }

        /// <summary>
        /// Obtenir le chemin complet de la couverture
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>

        public string GetCoverRelativeFilename(string filename)
        {
            return Path.Combine("Covers", filename);
        }

        /// <summary>
        /// Obtenir le chemin complet de la couverture
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>

        public string GetRomRelativeFilename(string filename)
        {
            return Path.Combine("Roms", filename);
        }

        /// <summary>
        /// Obtenir le chemin complet de la couverture
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>

        public string GetCoverFullFilename(string relativeFilename)
        {
            if (relativeFilename != null)
            {
                return Path.Combine(this.GameListViewModel.GameListPath, relativeFilename);
            }

            return null;
        }

        /// <summary>
        /// Obtenir le chemin complet de la couverture
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>

        public string GetRomFullFilename(string relativeFilename)
        {
            if (relativeFilename != null)
            {
                return Path.Combine(this.GameListViewModel.GameListPath, relativeFilename);
            }

            return null;
        }

        /// <summary>
        /// Aspirateur de Rom
        /// </summary>
        /// <param name="filename"></param>

        public void VacuumRom(string filename)
        {
            try
            {
                FileStream stream = File.OpenRead(filename);

                DotBoy.Gameboy.RomGB rom = new DotBoy.Gameboy.RomGB();

                rom.Load(stream);

                if (string.IsNullOrWhiteSpace(this.Label) == true)
                {
                    this.Label = rom.RomName;
                }

                string romNameNoSpace;
                
                romNameNoSpace = rom.RomName.Replace(" ", "");
                romNameNoSpace = rom.RomName.Replace("-", "");

                this.DownloadRomFile(filename);
                
                this.Id = string.Format("{0}-{1}", romNameNoSpace.ToLower(), rom.CheckSum);
            }
            catch(Exception ex)
            {
                throw new Exception("Error during the opening of the Rom : " + ex.Message);
            }
        }
    }
}
